package com.qiumo.help.context;

import cn.hutool.core.io.FileUtil;
import cn.hutool.core.io.IORuntimeException;
import cn.hutool.core.io.IoUtil;
import cn.hutool.core.text.CharSequenceUtil;
import cn.hutool.core.util.StrUtil;
import cn.hutool.http.HttpUtil;
import cn.hutool.json.JSONUtil;
import com.qiumo.help.util.FileTools;
import java.util.stream.Collectors;
import lombok.AccessLevel;
import lombok.Data;
import lombok.NoArgsConstructor;
import lombok.experimental.Accessors;
import lombok.extern.slf4j.Slf4j;

import java.io.File;
import java.io.IOException;
import java.io.InputStream;
import java.nio.charset.StandardCharsets;
import java.util.*;
import java.util.concurrent.CompletableFuture;

/**
 * JetBrains插件信息上下文管理器
 * 
 * <p>此类负责管理JetBrains插件市场中付费插件的信息，包括插件ID、名称、产品代码、定价模式等。
 * 插件信息通过JetBrains官方API实时获取，并缓存到本地JSON文件中，用于许可证生成时的插件选择。
 * 
 * <p>主要功能：
 * <ul>
 *   <li>从JetBrains插件市场API获取插件信息</li>
 *   <li>过滤和缓存付费插件信息</li>
 *   <li>定期刷新插件信息以保持最新状态</li>
 *   <li>为许可证生成提供插件产品代码</li>
 * </ul>
 * 
 * <p>数据流程：
 * <ol>
 *   <li>从本地JSON文件加载已缓存的插件信息</li>
 *   <li>定期调用JetBrains API获取最新插件列表</li>
 *   <li>过滤掉免费插件，只保留付费插件</li>
 *   <li>获取每个插件的详细信息（特别是产品代码）</li>
 *   <li>更新本地缓存文件</li>
 * </ol>
 * 
 * <p>API端点：
 * <ul>
 *   <li>插件列表：{@code https://plugins.jetbrains.com/api/searchPlugins}</li>
 *   <li>插件详情：{@code https://plugins.jetbrains.com/api/plugins/{pluginId}}</li>
 * </ul>
 * 
 * <p>缓存策略：
 * <ul>
 *   <li>本地JSON文件存储：{@code external/data/plugin.json}</li>
 *   <li>定时刷新：每天中午12点自动刷新一次</li>
 *   <li>增量更新：只获取新增的付费插件</li>
 * </ul>
 * 
 * @author QiuMo
 * @version 1.0.0
 * @since 1.0.0
 */

@Slf4j(topic = "插件上下文")
@NoArgsConstructor(access = AccessLevel.PRIVATE)
public class PluginsContextHolder {

    // ==================== 常量定义 ====================
    
    /** JetBrains插件市场基础URL */
    private static final String PLUGIN_BASIC_URL = "https://plugins.jetbrains.com";

    /** 插件列表API地址，获取所有插件基本信息 */
    private static final String PLUGIN_LIST_URL =
        PLUGIN_BASIC_URL + "/api/searchPlugins?max=10000&offset=0&orderBy=name";

    /** 插件详情API地址模板，用于获取单个插件的详细信息 */
    private static final String PLUGIN_INFO_URL = PLUGIN_BASIC_URL + "/api/plugins/";

    /** 插件信息缓存文件路径 */
    private static final String PLUGIN_JSON_FILE_NAME = "external/data/plugin.json";

    // ==================== 静态字段 ====================
    
    /** 插件信息缓存列表，存储所有已加载的付费插件信息 */
    private static List<PluginCache> pluginCacheList;

    /** 插件JSON缓存文件对象 */
    private static File pluginsJsonFile;

    // ==================== 核心方法 ====================

    /**
     * 初始化插件上下文
     * 
     * <p>此方法负责初始化插件信息管理系统，包括加载本地缓存和启动刷新任务。
     * 如果本地缓存文件不存在或格式错误，会自动从网络获取最新数据。
     * 
     * <p>初始化过程：
     * <ol>
     *   <li>获取或创建插件缓存JSON文件</li>
     *   <li>读取并验证JSON文件内容</li>
     *   <li>如果数据有效，解析为对象列表</li>
     *   <li>如果数据无效，初始化为空列表</li>
     *   <li>启动异步刷新任务获取最新数据</li>
     * </ol>
     * 
     * <p>注意事项：
     * <ul>
     *   <li>此方法应在应用启动时调用，且只调用一次</li>
     *   <li>网络刷新任务是异步执行的，不会阻塞应用启动</li>
     *   <li>刷新失败不会影响应用正常运行</li>
     * </ul>
     * 
     * @throws IllegalArgumentException 当文件读取失败时抛出
     */
    public static void init() {
        log.info("开始初始化插件上下文...");
        
        // 获取或创建插件缓存文件
        pluginsJsonFile = FileTools.getFileOrCreat(PLUGIN_JSON_FILE_NAME);
        log.debug("插件缓存文件路径: {}", pluginsJsonFile.getAbsolutePath());

        // 读取并验证JSON文件内容
        String pluginJsonArray;
        try {
            pluginJsonArray = IoUtil.readUtf8(FileUtil.getInputStream(pluginsJsonFile));
        } catch (IORuntimeException e) {
            throw new IllegalArgumentException(
                CharSequenceUtil.format("{} 文件读取失败!", PLUGIN_JSON_FILE_NAME), e);
        }
        
        // 检查JSON数据的有效性
        if (CharSequenceUtil.isBlank(pluginJsonArray) || !JSONUtil.isTypeJSON(pluginJsonArray)) {
            log.warn("插件缓存文件为空或格式错误，初始化为空列表");
            pluginCacheList = new ArrayList<>();
            // 启动异步刷新任务获取数据
            refreshJsonFile();
        } else {
            // 解析JSON数据为插件对象列表
            pluginCacheList = JSONUtil.toList(pluginJsonArray, PluginCache.class);
            log.info("插件上下文初始化成功！加载插件数量: {}", pluginCacheList.size());
            
            // 启动异步刷新任务获取最新数据
            refreshJsonFile();
        }
    }

    /**
     * 获取插件信息缓存列表
     * 
     * <p>返回已加载的所有JetBrains付费插件信息列表。
     * 该列表包含插件ID、名称、产品代码、定价模式和图标等信息。
     * 
     * <p>使用场景：
     * <ul>
     *   <li>许可证生成时选择插件产品代码</li>
     *   <li>前端界面显示插件列表</li>
     *   <li>插件管理和统计功能</li>
     * </ul>
     * 
     * <p>注意事项：
     * <ul>
     *   <li>返回的是实际的列表引用，修改会影响缓存</li>
     *   <li>列表中只包含付费插件，免费插件已被过滤</li>
     *   <li>需要在 {@link #init()} 方法调用后使用</li>
     * </ul>
     * 
     * @return 插件信息缓存列表，不为null
     */
    public static List<PluginCache> pluginCacheList() {
        return PluginsContextHolder.pluginCacheList;
    }

    public static void refreshJsonFile() {
        log.info("从'JetBrains.com'刷新中...");
        CompletableFuture
            .supplyAsync(PluginsContextHolder::pluginList)
            .thenApply(PluginsContextHolder::pluginListFilter)
            .thenApply(PluginsContextHolder::pluginConversion)
            .thenAccept(PluginsContextHolder::overrideJsonFile)
            .thenRun(() -> log.info("刷新成功!"))
            .exceptionally(throwable -> {
                log.error("刷新失败!", throwable);
                return null;
            });
    }

    public static void overrideJsonFile(List<PluginCache> pluginCaches) {
        log.info("源大小 => [{}], 新增大小 => [{}]", pluginCacheList.size(), pluginCaches.size());
        pluginCacheList.addAll(pluginCaches);
        String jsonStr = JSONUtil.toJsonStr(pluginCacheList);
        try {
            FileUtil.writeString(JSONUtil.formatJsonStr(jsonStr), pluginsJsonFile, StandardCharsets.UTF_8);
            log.info("Json文件已覆写!");
        } catch (IORuntimeException e) {
            throw new IllegalArgumentException(CharSequenceUtil.format("{} 文件写入失败!", PLUGIN_JSON_FILE_NAME), e);
        }

    }

    public static PluginList pluginList() {
        return HttpUtil.createGet(PLUGIN_LIST_URL)
            .thenFunction(response -> {
                try (InputStream is = response.bodyStream()) {
                    if (!response.isOk()) {
                        throw new IllegalArgumentException(
                            CharSequenceUtil.format("{} 请求失败! = {}", PLUGIN_LIST_URL, response));
                    }
                    PluginList pluginList = JSONUtil.toBean(IoUtil.readUtf8(is), PluginList.class);
                    log.info("获取大小 => [{}]", pluginList.getTotal());
                    return pluginList;
                } catch (IOException e) {
                    throw new IllegalArgumentException(CharSequenceUtil.format("{} 请求IO读取失败!", PLUGIN_LIST_URL),
                        e);
                }
            });
    }

    public static List<PluginList.Plugin> pluginListFilter(PluginList pluginList) {
        List<PluginList.Plugin> plugins = pluginList.getPlugins()
            .stream()
            .filter(plugin -> !PluginsContextHolder.pluginCacheList.contains(new PluginCache().setId(plugin.getId())))
            .filter(plugin -> !CharSequenceUtil.equals(plugin.getPricingModel(), "FREE"))
            .collect(Collectors.toList());
        log.info("过滤后大小 => [{}]", plugins.size());
        return plugins;
    }

    public static List<PluginCache> pluginConversion(List<PluginList.Plugin> pluginList) {
        List<PluginCache> list = pluginList
            .stream()
            .parallel()
            .map(plugin -> {
                String productCode = pluginInfo(plugin).getPurchaseInfo().getProductCode();
                return new PluginCache()
                    .setId(plugin.getId())
                    .setProductCode(productCode)
                    .setName(plugin.getName())
                    .setPricingModel(plugin.getPricingModel())
                    .setIcon(StrUtil.isNotBlank(plugin.getIcon()) ? PLUGIN_BASIC_URL + plugin.getIcon() : null)
                    ;
            })
            .collect(Collectors.toList());
        log.info("转换后大小 => [{}]", list.size());
        return list;
    }

    public static PluginInfo pluginInfo(PluginList.Plugin plugin) {
        return HttpUtil.createGet(PLUGIN_INFO_URL + plugin.getId())
            .thenFunction(response -> {
                try (InputStream is = response.bodyStream()) {
                    if (!response.isOk()) {
                        throw new IllegalArgumentException(
                            CharSequenceUtil.format("{} 请求失败! = {}", PLUGIN_INFO_URL, response));
                    }
                    PluginInfo pluginInfo = JSONUtil.toBean(IoUtil.readUtf8(is), PluginInfo.class);
                    log.info("已抓取 => ID = [{}], 名称 = [{}], Code = [{}]", pluginInfo.getId(), plugin.getName(),
                        pluginInfo.getPurchaseInfo().getProductCode());
                    return pluginInfo;
                } catch (IOException e) {
                    throw new IllegalArgumentException(CharSequenceUtil.format("{} 请求IO读取失败!", PLUGIN_LIST_URL),
                        e);
                }
            });
    }


    @Data
    @Accessors(chain = true)
    public static class PluginCache {

        private Long id;
        private String productCode;
        private String name;
        private String pricingModel;
        private String icon;

        @Override
        public final boolean equals(Object o) {
            if (this == o) {
                return true;
            }
            if (!(o instanceof PluginCache)) {
                return false;
            }

            return id.equals(((PluginCache) o).id);
        }

        @Override
        public int hashCode() {
            return id.hashCode();
        }
    }

    @Data
    @Accessors(chain = true)
    public static class PluginInfo {

        private Long id;

        private PurchaseInfo purchaseInfo;

        @Data
        @Accessors(chain = true)
        public static class PurchaseInfo {

            private String productCode;
        }
    }

    @Data
    @Accessors(chain = true)
    public static class PluginList {

        private List<Plugin> plugins;
        private Long total;


        @Data
        @Accessors(chain = true)
        public static class Plugin {

            private Long id;
            private String name;
            private String preview;
            private Integer downloads;
            private String pricingModel;
            private String organization;
            private String icon;
            private String previewImage;
            private Double rating;
            private VendorInfo vendorInfo;
        }

        @Data
        @Accessors(chain = true)
        public static class VendorInfo {

            private String name;
            private Boolean isVerified;
        }
    }
}
